package main;

/**
 * http://www.cricketschirping.com/processing/CirclePacking1/CirclePacking1.pde
 */
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

import data.DataManager;
import data.MailStat;
import processing.core.*;

public class MainWindow extends PApplet
{
	int PERCENTMULT = 150;
	PFont header;
	HScrollbar hs1;
	int scrollBarX = 50;
	ArrayList labels;

	Topic[] topics;
	ArrayList circles;

	boolean firstDraw = false;
	int percentIndex;
	public boolean zoom = false;
	public Circle zoomedCircle = null;
	public Keyword clickedKeyword = null;
	public Emails currentEmails = null;
	public boolean prevShow = false;
	public boolean nextShow = true;
	
	public int emailIndex = 0;

	
	String dateString1 = null;
	String dateString2 = null;

	DataManager dm = new DataManager();

	public void setup()
	{
		labels = dm.getLabelsForDateSelected();
		
		Topic[] topics = dm.setupTopics();
		
		// Start at the beginning
		percentIndex = 0;

		
		size(700, 700);
		circles = createCircles(topics);

		hs1 = new HScrollbar(scrollBarX, height - 20, 600, 10, 1);
		header = createFont("Helvetica", 48, true);
	}

	public void draw()
	{
		background(255);
		if (firstDraw != true)
		{
			for (int i = 0; i < circles.size(); i++)
			{
				getCircle(i).draw();
			}
			for (int i = 1; i < 50; i++)
			{
				iterateLayout(i);
			}
			firstDraw = true;
		}
		
		fill(255);

		hs1.update();
		hs1.display();

		
		// Zoomed
		if (zoom)
		{
			zoomedCircle.draw();
			
			// Current Emails are showing
			if (currentEmails != null)
			{
				currentEmails.draw();
			}
		} 
		
		// Draw All Circles
		else
		{
			for (int i = 0; i < circles.size(); i++)
			{
				getCircle(i).draw();
			}
			for (int i = 1; i < 50; i++)
			{
				iterateLayout(i);
			}
		}
	}
	/**
	 * 
	 * Horizontal Scrollbar class
	 *
	 */
	class HScrollbar
	{
		int swidth, sheight; // width and height of bar
		int xpos, ypos; // x and y position of bar
		float spos, newspos; // x position of slider
		int sposMin, sposMax; // max and min values of slider
		int loose; // how loose/heavy
		boolean over; // is the mouse over the slider?
		boolean locked;
		float ratio;

		HScrollbar(int xp, int yp, int sw, int sh, int l)
		{
			swidth = sw;
			sheight = sh;
			int widthtoheight = sw - sh;
			ratio = (float) sw / (float) widthtoheight;
			xpos = xp;
			ypos = yp - sheight / 2;
			spos = xpos + swidth / 2 - sheight / 2;
			newspos = spos;
			sposMin = xpos;
			sposMax = xpos + swidth - sheight;
			loose = l;
		}

		void update()
		{
			if (over())
			{
				over = true;
			} else
			{
				over = false;
			}
			if (mousePressed && over)
			{
				locked = true;
			}
			if (!mousePressed)
			{
				locked = false;
			}
			if (locked)
			{
				newspos = constrain(mouseX - sheight / 2, sposMin, sposMax);
			}
			if (abs(newspos - spos) > 1)
			{
				spos = spos + (newspos - spos) / loose;
			}
		}

		int constrain(int val, int minv, int maxv)
		{
			return min(max(val, minv), maxv);
		}

		boolean over()
		{
			if (mouseX > xpos && mouseX < xpos + swidth && mouseY > ypos
					&& mouseY < ypos + sheight)
			{
				return true;
			} else
			{
				return false;
			}
		}

		/**
		 * Displays the scrollbar
		 * Puts a label underneath the scroll bar
		 */
		void display()
		{
			fill(255);
			rect(xpos, ypos, swidth, sheight);
			if (over || locked)
			{
				fill(153, 102, 0);

			} else
			{
				fill(102, 102, 102);
			}
			rect(spos, ypos, sheight, sheight);

			// Put text label under scroll bar
			textFont(header, 10);
			fill(0);
			percentIndex = (int) Math.ceil((labels.size()-1)
					* ((spos - scrollBarX) / swidth));
			dateString1 = (String) labels.get(percentIndex);
			
			if (percentIndex != labels.size() - 1)
			{
				dateString2 = (String) labels.get(percentIndex + 1);
				text(dateString1 + " to "
						+ dateString2, spos - 60,
						ypos + 22);
			} else
			{
				text(dateString1, spos, ypos + 25);
			}

		}

		float getPos()
		{
			// Convert spos to be values between
			// 0 and the total width of the scrollbar
			return spos * ratio;
		}
	}

	Comparator comp = new Comparator()
	{
		public int compare(Object p1, Object p2)
		{
			Circle a = (Circle) p1;
			Circle b = (Circle) p2;
			if (a.distanceToCenter() < b.distanceToCenter())
				return 1;
			else if (a.distanceToCenter() > b.distanceToCenter())
				return -1;
			else
				return 0;
		}
	};
	/**
	 * Circle packing algorithm from:
	 * http://www.cricketschirping.com/processing/CirclePacking1/
	 * 
	 * Takes care of the physics and makes sure the circle stay tightly packed.
	 * @param iterationCounter
	 */
	void iterateLayout(int iterationCounter)
	{

		Object circs[] = circles.toArray();
		Arrays.sort(circs, comp);

		// fix overlaps
		Circle ci, cj;
		PVector v = new PVector();

		for (int i = 0; i < circs.length; i++)
		{
			ci = (Circle) circs[i];
			for (int j = i + 1; j < circs.length; j++)
			{
				if (i != j)
				{
					cj = (Circle) circs[j];
					float dx = cj.x - ci.x;
					float dy = cj.y - ci.y;
					float r = ci.radius + cj.radius;
					float d = (dx * dx) + (dy * dy);
					if (d < (r * r) - 0.01)
					{

						v.x = dx;
						v.y = dy;

						v.normalize();
						v.mult((float) ((r - sqrt(d)) * 0.5));

						if (cj != dragCircle)
						{
							cj.x += v.x;
							cj.y += v.y;
						}

						if (ci != dragCircle)
						{
							ci.x -= v.x;
							ci.y -= v.y;
						}
					}
				}
			}
		}

		// Contract
		float damping = (float) (0.1 / (float) (iterationCounter));
		for (int i = 0; i < circs.length; i++)
		{
			Circle c = (Circle) circs[i];
			if (c != dragCircle)
			{
				v.x = c.x - width / 2;
				v.y = c.y - height / 2;
				v.mult(damping);
				c.x -= v.x;
				c.y -= v.y;
			}
		}
	}

	ArrayList createCircles(Topic[] topics)
	{
		ArrayList circles = new ArrayList();
		colorMode(HSB, 255);
		for (int i = 0; i < topics.length; i++)
		{
			int radius = 30 + (int)(PERCENTMULT * topics[i].percents[percentIndex]);
			Circle c = new Circle(random(width), random(height), radius,
					topics[i]);
			circles.add(c);
		}
		colorMode(RGB, 255);
		return circles;
	}

	Circle getCircle(int i)
	{
		return (Circle) circles.get(i);
	}

	Circle dragCircle = null;

	
	
	public void mousePressed()
	{

		// Not zoomed in, find the circle that was clicked and zoom.
		if (!zoom)
		{
			for (int i = 0; i < circles.size(); i++)
			{
				Circle c = getCircle(i);
				if (c.contains(mouseX, mouseY))
				{
					c.zoom();
				}
			}
		}
		// Zoomed In
		else
		{
			// Emails are showing
			if(currentEmails != null){
				if(currentEmails.exitClicked(mouseX, mouseY)){
					currentEmails = null;
					emailIndex = 0;
				}
				else{
					currentEmails.checkNextClicked(mouseX, mouseY);
					currentEmails.checkPrevClicked(mouseX, mouseY);
				}
				
				
			}
			// No emails are showing
			else if (!zoomedCircle.contains(mouseX, mouseY))
			{
				zoomedCircle.zoom();
				zoom = false;
				zoomedCircle = null;
			} else
			{
				for (int i = 0; i < zoomedCircle.keywords.length; i++)
				{
					if (zoomedCircle.keywords[i].contains(mouseX, mouseY))
					{
						// Get Emails for specific week
						
						Emails keywordEmails = new Emails();
						keywordEmails.emails = MailStat.getEmailsFromDate(dateString1 + " to " + dateString2 ,zoomedCircle.keywords[i].keyword);
						currentEmails = keywordEmails;
					}
				}
			}
		}

	}

	public void mouseDragged()
	{
	}

	public void mouseReleased()
	{
	}

	class Circle
	{
		public float x, y, radius;
		public double[] percents;
		public Keyword[] keywords;
		public int r;
		public String label;

		public Circle(float x, float y, float radius, Topic topic)
		{
			this.x = x;
			this.y = y;
			this.radius = radius;
			this.percents = topic.percents;
			this.r = (int) random(255);
			this.label = topic.label;

			this.keywords = new Keyword[topic.keywords.length];

			int xMid = width / 2;
			int yMid = height / 2;
			int yOffset = yMid + 25;

			for (int i = 0; i < topic.keywords.length; i++)
			{
				yOffset = yOffset + 20;
				keywords[i] = new Keyword(xMid - 20, yOffset,
						topic.keywords[i], null);
			}
		}

		public void draw()
		{
			if (!zoom)
			{
				radius = 30 + (int)(PERCENTMULT * percents[percentIndex]);
			}

			fill(r, 128, 200, 128);
			stroke(64, 64, 64, 64);
			strokeWeight(3);
			ellipse((int) x, (int) y, (int) radius * 2, (int) radius * 2);
			fill(255);
			if (!zoom)
			{
				textFont(header, 14);
				text(label, (int) x - 30, (int) y);
			} else
			{
				textFont(header, 30);
				text(label, (int) x - 30, (int) y);
				for (int i = 0; i < keywords.length; i++)
				{
					keywords[i].draw();
				}

			}
		}

		public void zoom()
		{
			if (zoom)
			{
				radius = 30 + (int)(PERCENTMULT * percents[percentIndex]);
				zoom = false;
				zoomedCircle = null;
				currentEmails = null;
			} else
			{
				zoom = true;
				zoomedCircle = this;
				radius = width / 2 - 50;
				x = width / 2;
				y = width / 2;
			}
			ellipse((int) x, (int) y, (int) radius * 2, (int) radius * 2);
		}

		public boolean contains(float x, float y)
		{
			float dx = this.x - x;
			float dy = this.y - y;
			return sqrt(dx * dx + dy * dy) <= radius;
		}

		public float distanceToCenter()
		{
			float dx = x - WIDTH / 2;
			float dy = y - HEIGHT / 2;
			return (sqrt(dx * dx + dy * dy));
		}

		public boolean intersects(Circle c)
		{
			float dx = c.x - x;
			float dy = c.y - y;
			float d = sqrt(dx * dx + dy * dy);
			return d < radius || d < c.radius;
		}
	}

	class Keyword
	{
		public int x, y;
		public String keyword;
		public String[] emails;
		public float textWidth;

		public Keyword(int x, int y, String keyword, String[] emails)
		{
			this.x = x;
			this.y = y;
			this.keyword = keyword;
			this.emails = emails;
			this.textWidth = textWidth(keyword);
		}

		public void draw()
		{
			fill(255);
			textFont(header, 15);
			text(keyword, x, y);
		}

		public boolean contains(float mX, float mY)
		{
			return (mX > x // To the right
					&& mX < x + textWidth // to the left
					&& mY < y // To the top
			&& mY > y - 9); // To the bottom
		}
	}
	
	class Emails{
		public int x, y;
		public String[] emails;
		
		public Emails(){
			emailIndex = 0;
		}
		
		public void draw()
		{
			
			// Background Rectangle
			fill(0,0,0,200);
			rect(10, 10, width-10, height-10);
					
			// Previous
			if(emailIndex != 0){
				fill(255);
				textFont(header, 20);
				text("PREV", 15, 27);
				prevShow = true;
			}
			else{
				prevShow = false;
			}
			
			//Next
			if(emailIndex != emails.length - 1 && emails.length > 0){
				fill(255);
				textFont(header, 20);
				text("NEXT", 15+textWidth("PREV")+20, 27);
				nextShow = true;
			}
			else{
				nextShow = false;
			}
			
			//Exit
			fill(255);
			textFont(header, 20);
			text("EXIT", 15 + 300, 27);
			
			if(emails != null && emails.length != 0){
				if( emails[emailIndex] != null){
				fill(255);
				textAlign(LEFT);
				textFont(header, 14);
				text(emails[emailIndex], 17, 40, width-20, height - 20);
			}}
			else{
				text("ERROR: Couldn't find emails with that keyword for specific week", 17, 40, width-20, height - 20);
			}
		}
		
		public void nextEmail(){
			emailIndex++;
			
		}
		
		public void prevEmail(){
			emailIndex--;
		}
		
		public void checkPrevClicked(float mX, float mY){
			if(prevShow){
				textFont(header, 20);
				System.out.println("MouseX: " + mX + " MouseY: " + mY);
				if( (mX > 17 // To the right
						&& mX < 17 + textWidth("PREV") // to the left
						&& mY < 27 // To the top
				&& mY > 27 - 13)){ // To the bottom
					prevEmail();
				}
			}

		}
		
		public void checkNextClicked(float mX, float mY){
			System.out.println("MouseX: " + mX + " MouseY: " + mY);
			if(nextShow){
				textFont(header, 20);

				if( (mX > 17 + textWidth("PREV") + 20 // To the right
						&& mX < 17 + textWidth("PREV") + 20 + textWidth("NEXT") // to the left
						&& mY < 27 // To the top
				&& mY > 27 - 13)){ // To the bottom
					nextEmail();
				}
			}
		}
		
		public boolean exitClicked(float mX, float mY){
			System.out.println("MouseX: " + mX + " MouseY: " + mY);
			textFont(header, 20);
		    return (mX > 17 + 300  // To the right
						&& mX < 17 + 300 +  textWidth("EXIT") // to the left
						&& mY < 27 // To the top
				&& mY > 27 - 13); // To the bottom
		}
		
		
	}

}
